package ltd.sshine.networkserver.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import lombok.extern.slf4j.Slf4j;
import ltd.sshine.networkcommon.config.ConnectConfig;
import ltd.sshine.networkcommon.constraint.ChannelConstraint;
import ltd.sshine.networkcommon.vo.ConnectionVO;
import ltd.sshine.networkserver.config.ServerConfig;
import ltd.sshine.networkserver.exception.PortCheckException;
import ltd.sshine.networkserver.factory.ProxyServerFactory;
import ltd.sshine.networkserver.holder.ClientConnectHolder;
import ltd.sshine.networkserver.holder.ProxyServerHolder;
import ltd.sshine.networkserver.holder.ServerConnectHolder;
import ltd.sshine.networkserver.port.PortUtils;
import ltd.sshine.networkserver.utils.BeanUtils;
import ltd.sshine.networkserver.utils.ResultUtils;

import java.net.BindException;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class ServerAuthChannelHandler extends ChannelInboundHandlerAdapter {
    private final ChannelHandler[] authUseHandler;
    private final ObjectMapper objectMapper;
    private final PortUtils portUtils;
    private final ServerConfig serverConfig;

    {
        objectMapper = BeanUtils.getBean(ObjectMapper.class);
        portUtils = BeanUtils.getBean(PortUtils.class);
        serverConfig = BeanUtils.getBean(ServerConfig.class);
    }

    public ServerAuthChannelHandler(ChannelHandler... authUseHandler) {
        this.authUseHandler = authUseHandler;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf bf = (ByteBuf) msg;
        String s = bf.toString(StandardCharsets.UTF_8);
        ConnectConfig config = objectMapper.readValue(s, ConnectConfig.class);
        if (serverConfig.getUsername() != null && !"".equals(serverConfig.getUsername())) {
            if (config.getPassword() == null) {
                config.setPassword("");
            }
            if (serverConfig.getPassword() == null) {
                serverConfig.setPassword("");
            }
            if (!config.getUsername().equals(serverConfig.getUsername()) ||
                 !config.getPassword().equals(serverConfig.getPassword())) {
                ctx.channel().writeAndFlush(objectMapper.writeValueAsString(ResultUtils.error("用户名或密码不正确")));
                ctx.close();
                return;
            }

        }
        log.debug("收到-{}", config);
        if (config.getProxy().getPort() == null) {
            config.getProxy().setPort(List.of("8080"));
        }
        int port;
        List<Integer> ports = ClientConnectHolder.getPorts();
        try {
            while ((port = portUtils.genPortUtils(config.getProxy(), ports)) != -1) {
                try {
                    HttpProxyChannelHandler httpProxyChannelHandler = ProxyServerFactory.create(port);
                    ConnectionVO data = new ConnectionVO();
                    data.setPort(port);
                    ctx.channel().writeAndFlush(objectMapper.writeValueAsString(ResultUtils.success(data)));
                    ClientConnectHolder.add(port, ctx.channel());
                    buildNewChannel(ctx, httpProxyChannelHandler, port);
                    break;
                } catch (BindException e) {
                    ports.add(port);
                }
            }
            if (port == -1) {
                ctx.channel().writeAndFlush(objectMapper.writeValueAsString(ResultUtils.error("没有可用端口")));
                ctx.close();
            }
        }catch (PortCheckException e){
            ctx.channel().writeAndFlush(objectMapper.writeValueAsString(ResultUtils.error(e.getMessage())));
            ctx.close();
        }

    }

    public void buildNewChannel(ChannelHandlerContext ctx, HttpProxyChannelHandler httpProxyChannelHandler, int port) {
        ctx.pipeline().addLast(
                new HttpClientCodec(),
                new HttpObjectAggregator(1024 * 1024 * 1024),
                new SimpleChannelInboundHandler<>() {
                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                        FullHttpResponse fullHttpResponse = (FullHttpResponse) msg;
                        String s = fullHttpResponse.headers().get(ChannelConstraint.CHANNEL_ID);
                        Channel channel = ServerConnectHolder.getChannel(s);
                        channel.pipeline().lastContext().writeAndFlush(fullHttpResponse.copy());
                    }

                    @Override
                    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                        ClientConnectHolder.delete(port);

                        ProxyServerHolder.removeChannel(httpProxyChannelHandler);
                        super.channelInactive(ctx);
                    }
                    @Override
                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        ClientConnectHolder.delete(port);
                        ProxyServerHolder.removeChannel(httpProxyChannelHandler);
                        if (cause instanceof SocketException){
                            return;
                        }
                        super.exceptionCaught(ctx, cause);
                    }
                });
        httpProxyChannelHandler.setClientCtx(ctx.pipeline().lastContext());
        Arrays.stream(authUseHandler).forEach(ctx.pipeline()::remove);
        ctx.pipeline().remove(this);

    }

}
